using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace Columbus
{
    public static class NavigatorExtensions
    {
        public static void NavigateTo<TController>(this INavigator navigator, Expression<Func<TController, ActionResult>> actionSelector) where TController : Controller
        {
            if (navigator == null) throw new ArgumentNullException("navigator");
            if (actionSelector == null) throw new ArgumentNullException("actionSelector");

            var navParameters = new List<NavigationParameter>();
            var controller = typeof(TController);
            var controllerName = controller.Name;

            var body = actionSelector.Body as MethodCallExpression;
            if (body == null)
            {
                throw new Exception("The lambda expression used for navigation could not be parsed. The lambda should be a MethodCallExpression, for example: 'x => x.Search(Text)'.");
            }

            var method = body.Method;
            var actionName = method.Name;

            try
            {
                var parameters = method.GetParameters();
                var arguments = body.Arguments;
                for (var i = 0; i < parameters.Length && i < arguments.Count; i++)
                {
                    var parameter = parameters[i].Name;
                    var argument = arguments[i];
                    var lambda =
                        Expression.Lambda<Func<TController, object>>(Expression.Convert(argument, typeof(object)),
                                                                     actionSelector.Parameters.ToList());
                    var compiled = lambda.Compile();
                    var value = compiled(default(TController));
                    navParameters.Add(new NavigationParameter(parameter, value));
                }
            }
            catch (Exception ex)
            {
                throw new Exception("Error while trying to get parameters from lambda expression. Please note that due to WP7 platform reflection limitations (it does not allow to get/set private member values) private fields/properties/methods/etc can not be used in parameter closures, same applies to local method variables. Consider making closure values available via public members or passing constants if possible; otherwise please use other overloads of NavigateTo method to avoid these limitations. Also see inner exception for more details.", ex);
            }

            navigator.Navigate(new NavigationRequest(navigator.NavigationFrameName, controllerName, actionName, navParameters.ToArray()));
        }

        public static void NavigateTo<TController>(this INavigator navigator, string actionName, params object[] parameters) where TController : Controller
        {
            if (navigator == null) throw new ArgumentNullException("navigator");
            if (actionName == null) throw new ArgumentNullException("actionName");

            var controller = typeof(TController);
            var controllerName = controller.Name;
            navigator.NavigateTo(controllerName, actionName, parameters);
        }

        public static void NavigateTo(this INavigator navigator, string controllerName, string actionName, params object[] parameters)
        {
            if (navigator == null) throw new ArgumentNullException("navigator");
            if (controllerName == null) throw new ArgumentNullException("controllerName");
            if (actionName == null) throw new ArgumentNullException("actionName");

            navigator.Navigate(new NavigationRequest(navigator.NavigationFrameName, controllerName, actionName, parameters.Select(p => new NavigationParameter(p)).ToArray()));
        }
    }
}